package com.ruoyi.service.app;

import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.ruoyi.common.config.MiniProgramConfig;
import com.ruoyi.common.constant.RedisKeyConstants;
import com.ruoyi.common.core.domain.entity.app.AppUserLoginDB;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.global.CustomException;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.system.mapper.app.AppUserLoginMapper;
import com.ruoyi.system.vo.MiniProgramLoginVO;
import com.ruoyi.system.vo.WxSessionResp;
import com.ruoyi.util.jwt.SecurityUtils;
import com.ruoyi.util.jwt.WxJwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.Instant;
import java.util.Date;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class UserLoginService {
    @Resource
    private MiniProgramConfig config;
    @Resource
    private TokenService tokenService;
    @Resource
    private RedisCache redisCache;
    @Resource
    private AppUserLoginMapper appUserLoginMapper;
    @Value("${token.secret}")
    private String secret;
    @Resource
    private AppCommonService appCommonService;

    public WxSessionResp jscode2session(String code) {
        String url = StrUtil.format(
                "https://api.weixin.qq.com/sns/jscode2session?appid={}&secret={}&js_code={}&grant_type=authorization_code",
                config.getAppId(), config.getAppSecret(), code);


        HttpResponse resp = HttpRequest.get(url)
                // 3 秒超时
                .timeout(3000)
                .execute();

        if (!resp.isOk()) {
            log.error("微信接口网络异常，状态码：{}", resp.getStatus());
            throw new CustomException("微信小程序登录失败");
        }

        // 把微信返回的 json 直接转对象
        WxSessionResp dto = JSONUtil.toBean(resp.body(), WxSessionResp.class);

        // 微信错误码判断
        if (dto.getErrcode() != null && !"0".equals(dto.getErrcode())) {
            log.error("微信错误码：{}，错误信息：{}", dto.getErrcode(), dto.getErrmsg());
            throw new CustomException("微信小程序登录失败：" + dto.getErrmsg());
        }
        return dto;
    }

    public MiniProgramLoginVO miniProgramLogin(String code) {
        // 过期时间(分钟)
        int expireTime = tokenService.getExpireTime();
        // 转为秒
        int expireTimeSecond = expireTime * 60;

        WxSessionResp wxSessionResp = jscode2session(code);
        // 获取到了用户的openId;
        String openid = wxSessionResp.getOpenid();
        // 首先 TokenService 查看redis中的数据，若是有，则使用，若是没有，则重新生成并且缓存
        String tokenByOpenid = getRedisTokenByOpenid(openid);
        if (ObjectUtils.isNotEmpty(tokenByOpenid)) {
            return MiniProgramLoginVO.builder().openid(openid).code(code).token(tokenByOpenid).build();
        }
        String token = WxJwtUtil.generate(openid, secret, expireTimeSecond);
        initRedisToken(openid, token, expireTimeSecond);

        // 过期日期 = 当前时间+ 过期时间（秒）
        Date expireTimeDate = Date.from(Instant.now().plusSeconds(expireTimeSecond));


        MiniProgramLoginVO build = MiniProgramLoginVO.builder().openid(openid).code(code).token(token).expireTime(expireTimeDate).build();
        // 保存数据库
        this.saveUserLoginInfo(build);
        // 保存到redis中(用户openId:用户id)用户快速定位用户id
        saveRedisOpenIdAndUserIdRelation(openid);
        return build;
    }

    private String getRedisTokenByOpenid(String openid) {
        String key = RedisKeyConstants.USER_TOKEN_KEY + openid;
        // 获取过期时间
        long expire = 0;
        try {
            expire = redisCache.getExpire(key);
        } catch (Exception e) {
            throw new CustomException("redis缓存用户token失败");
        }
        // 小于一小时，认为已经过期
        if (expire <= 3600) {
            return null;
        }
        return redisCache.getCacheObject(key);
    }

    private void initRedisToken(String openid, String token, Integer expireTimeSecond) {
        String key = RedisKeyConstants.USER_TOKEN_KEY + openid;
        try {
            redisCache.setCacheObject(key, token, expireTimeSecond, TimeUnit.SECONDS);
        } catch (Exception e) {
            log.error("redis缓存用户token失败", e);
        }
    }

    // 每次登录都保存一次，覆盖之前的
    private void saveRedisOpenIdAndUserIdRelation(String openId){
        AppUserLoginDB userByMiniOpenId = appCommonService.getUserByMiniOpenId(openId);
        if (ObjectUtils.isEmpty(userByMiniOpenId)){
            throw new CustomException("用户不存在，请先注册");
        }
        String key = RedisKeyConstants.OPEN_ID_KEY + openId;
        redisCache.setCacheObject(key, userByMiniOpenId.getId(), 30, TimeUnit.DAYS);
//        Object cacheObject = redisCache.getCacheObject(key);
//        System.out.println("查询："+Long.valueOf(cacheObject.toString()));
    }

    private void saveUserLoginInfo(MiniProgramLoginVO loginVO) {
        // 查询该openId是否存在，若是存在则更新，若是不存在则插入
        if (appCommonService.checkUserExistsByMiniOpenId(loginVO.getOpenid())){
            LambdaUpdateWrapper<AppUserLoginDB> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.eq(AppUserLoginDB::getMiniOpenid, loginVO.getOpenid());
            updateWrapper.set(AppUserLoginDB::getLastLoginTime, new Date());
            // Integer loginNumber 加一
            updateWrapper.setSql("login_number = login_number + 1");
            appUserLoginMapper.update(null, updateWrapper);
        }else {
            // 插入
            AppUserLoginDB appUserLoginDB = new AppUserLoginDB();
            appUserLoginDB.setMiniOpenid(loginVO.getOpenid());
            appUserLoginDB.setLastLoginTime(new Date());
            appUserLoginDB.setTenantId(Long.valueOf(SecurityUtils.getRequestHead().getTenantId()));
            appUserLoginMapper.insert(appUserLoginDB);
        }
    }

}
